#include <stdio.h>
#include "astree.h"
#include "tacs.h"

extern table symbolsTable;

// PROTOTYPES
TAC* generateCode(node* node);
TAC* make_unop(int type, TAC* code0);
TAC* make_binop(int type, TAC* code0, TAC* code1);
TAC* make_if_then_else(TAC* code0, TAC* code1, TAC* code2);
TAC* make_loop(TAC* code0, TAC* code1);
TAC* make_fundef(TAC* code0, TAC* code1, TAC* code2);
TAC* make_initVar(TAC* code0, TAC* code1, TAC* code2);
TAC* make_initVec(TAC* code0, TAC* code1, TAC* code2);

// IMPLEMENTATION

TAC* generateCode(node* node) {
	//fprintf(stderr, "\n Entrou em generateCode()");
	int i;
	TAC *code[MAX_SONS];
	TAC* result = NULL;

	if(node == NULL) {
		//fprintf(stderr, "\n Entered NULL");
		return 0;
	}

	//fprintf(stderr, " \n genCode() node->type = %d ", node->type);
	for(i=0; i<MAX_SONS; i++) {
		code[i] = NULL;
	}
	// PROCESS CHILDREN FIRST RECURSIVELLY
	//fprintf(stderr, "\nSWITCH de %d!", node->type);	
	for(i=0; i<MAX_SONS; i++) {
		if(node->chld[i] != NULL) {
			//fprintf(stderr, "\n Start with Filho %d de %d ", i, node->type);
			code[i] = generateCode(node->chld[i]);
			//fprintf(stderr, "\n DONE Filho %d de %d ", i, node->type);
		}
		else {
			//fprintf(stderr, "\nFilho %d de %d is null", i, node->type);
			code[i] = NULL;
		}
	}
	
	//fprintf(stderr, "\nSWITCH de %d!", node->type);
	switch(node->type) {
		// CASE FOR EACH ASTREE NODE TYPE
		case AST_IDENTIFIER:
			if(node->chld[0] != NULL) {
				result = tac_join(code[1], tac_create(TAC_VECR, makeTemp(&symbolsTable), node->chld[0]->value, code[1]->target));
			}
			else {
				result = tac_create(TAC_SYMBOL, node->value, 0, 0);
			}
			break;
		case AST_LITERAL:
			result = tac_create(TAC_SYMBOL, node->value, 0, 0); //node->symbol é ponteiro pra tabela
			break;
	
		case AST_OP_REF: result = make_unop(TAC_REF, code[0]); break;
		case AST_OP_DEREF: result = make_unop(TAC_DEREF, code[0]); break;
		case AST_OP_ADD: result = make_binop(TAC_ADD, code[0], code[1]); break;
		case AST_OP_SUB: result = make_binop(TAC_SUB, code[0], code[1]); break;
		case AST_OP_MUL: result = make_binop(TAC_MUL, code[0], code[1]); break;
		case AST_OP_DIV: result = make_binop(TAC_DIV, code[0], code[1]); break;
		case AST_OP_LE: result = make_binop(TAC_LE, code[0], code[1]); break;
		case AST_OP_GE: result = make_binop(TAC_GE, code[0], code[1]); break;
		case AST_OP_EQ: result = make_binop(TAC_EQ, code[0], code[1]); break;
		case AST_OP_NE: result = make_binop(TAC_NE, code[0], code[1]); break;
		case AST_OP_AND: result = make_binop(TAC_AND, code[0], code[1]); break;
		case AST_OP_OR: result = make_binop(TAC_OR, code[0], code[1]); break;
		case AST_OP_G: result = make_binop(TAC_G, code[0], code[1]); break;
		case AST_OP_L: result = make_binop(TAC_L, code[0], code[1]); break;

		case AST_DEF_FUNC: result = make_fundef(code[0], code[2], code[3]);break;
		case AST_EXPR_FUNCTION_CALL: //result = tac_join(tac_join(code[0], code[1]), tac_create(TAC_CALL, 0, (code[0] != NULL) ? code[0]->target : NULL, NULL));
		        
		    result =  tac_join(tac_join(code[0], code[1]), make_unop(TAC_CALL, code[0])) ;
		    break;

		/* Types */
		case TYPE_WORD: result = NULL; break;
		case TYPE_BOOL: result = NULL; break;
		case TYPE_BYTE: result = NULL; break;

		/* Lists */
		case AST_LST_CMD: result = tac_join(code[0], code[1]); break;
		case AST_LST_PRINT_VAL: result = tac_join(tac_join(code[0], code[1]), tac_create(TAC_PRINT, (code[1] != NULL) ? code[1]->target : NULL, 0, 0));break;
		case AST_LST_GBL_DEF: result = tac_join(code[0], code[1]); break;
		case AST_LST_INIT_VEC: result = tac_join(code[0], code[1]); break;
		case AST_LST_LOCAL_VAR: result = tac_join(code[0], code[1]); break;
		case AST_LST_PARAM_FUNC: result = tac_join(code[0], code[1]); break;
		case AST_LST_PARAM_FUNC_CALL: result = tac_join(tac_join(code[0], code[1]), tac_create(TAC_ARG, (code[1] != NULL) ? code[1]->target : NULL, 0, 0)); break;

		/* Comands */
		//ver como fica essa attr pra vetor:
		case AST_CMD_ATTR:
			if(code[0]->type == TAC_VECR) {
				result = tac_join(code[1], tac_create(TAC_VECW, code[0]->op1, code[0]->op2, code[1]->target));
			}
			else{
				result = tac_join(code[1],tac_create(TAC_MOVE, code[1]->target, (code[0] != NULL) ? code[0]->target : NULL, 0));
			}
			break;
		case AST_CMD_INPUT: result = tac_create(TAC_READ, (code[0] != NULL) ? code[0]->target : NULL, 0, 0);break;
		//case AST_CMD_OUTPUT: result = tac_join(code[0], code[1]);break;
		case AST_CMD_RETURN: result = tac_join(code[0], tac_create(TAC_RET, code[0]->target, 0, 0)); break;
		case AST_CMD_IF: result = make_if_then_else(code[0], code[1], code[2]); break;
		case AST_CMD_LOOP: result = make_loop(code[0], code[1]); break;
		case AST_CMD_FUNCTION_CALL: //result = tac_join(tac_join(code[0], code[1]), tac_create(TAC_CALL, 0, (code[0] != NULL) ? code[0]->target : NULL, NULL)); break;
		    result = tac_join(tac_join(code[0], code[1]), make_unop(TAC_CALL, code[0]));
		    break;
		
		
		//case AST_CMD_BLOCK: break;

		/* Inicializações */
		case AST_DEF_GLOBAL_VAR: result = (code[3]==NULL) ? 
										  tac_join(tac_join(tac_join(code[0], code[1]), code[2]), make_initVar(code[0], code[1], code[2]))
										  :
 										  make_initVec(code[0], code[2], code[3]);
										  break;
		case AST_DEF_UBIQ_VAR: result = tac_join(tac_join(tac_join(code[0], code[1]), code[2]), make_initVar(code[0], code[1], code[2])); break;

		default:
			//fprintf(stderr, "\nDefault!");
			result = tac_join(tac_join(tac_join(code[0], code[1]), code[2]), code[3] );
			//result = NULL;		
			break;
	}
	
	return result;
}

TAC* make_unop(int type, TAC* code0) {
	return tac_create(type, makeTemp(&symbolsTable), (code0 != NULL) ? code0->target : NULL, NULL);
}

TAC* make_binop(int type, TAC* code0, TAC* code1) {
	/*
	TAC* result;
	TAC* nova;

	tac_join(code0, code1);	//tac_join() tolerante a ponteiros nulos :)
	nova = tac_create(type , makeTemp(&symbolsTable), (code0 != NULL) ? code0->target : NULL, (code1 != NULL) ? code1->target : NULL);
	nova->prev = result;
	result = nova;
	return result;
	*/
	return tac_join(tac_join(code0, code1),
		             tac_create(type , makeTemp(&symbolsTable), (code0 != NULL) ? code0->target : NULL, (code1 != NULL) ? code1->target : NULL));
}

/*
TAC* makeIfThen(TAC* code0, TAC* code1) {
	TAC* nova1;
	TAC* nova2;
	row* label;

	nova1 = tac_create(TAC_JZ, label = makeLabel(&symbolsTable), (code0!=NULL)?code0->target:NULL, 0);	//Criar tipo TAC_JZ
	nova2 = tac_create(TAC_LABEL, label, 0, 0);											//Criar tipo TAC_LABEL

	return tac_join(tac_join(tac_join(code0, nova1), code1), nova2);
}
*/

TAC* make_if_then_else(TAC* code0, TAC* code1, TAC* code2) {
	TAC* nova1;
	TAC* nova2;
	TAC* nova3;
	TAC* nova4;
	row* labelIFZ;
	row* labelJMP;

	nova1 = tac_create(TAC_IFZ, labelIFZ = makeLabel(&symbolsTable), (code0!=NULL)?code0->target:NULL, 0);	//Criar tipo TAC_JZ
	nova2 = tac_create(TAC_LABEL, labelIFZ, 0, 0);											//Criar tipo TAC_LABEL
	
	if(code2 != NULL) {
		nova3 = tac_create(TAC_JUMP, labelJMP = makeLabel(&symbolsTable), 0, 0);
		nova4 = tac_create(TAC_LABEL, labelJMP, 0, 0);
		return tac_join(tac_join(tac_join(tac_join(tac_join(tac_join(code0, nova1), code1), nova3), nova2), code2), nova4); 
	}

	return tac_join(tac_join(tac_join(code0, nova1), code1), nova2);
}

TAC* make_loop(TAC* code0, TAC* code1) {
	TAC* nova1;
	TAC* nova2;
	TAC* nova3;
	TAC* nova4;
	row* labelIFZ;
	row* labelJMP;

	labelJMP = makeLabel(&symbolsTable);
	labelIFZ = makeLabel(&symbolsTable);
	
	nova1 = tac_create(TAC_LABEL, labelJMP, 0, 0);
	nova2 = tac_create(TAC_IFZ, labelIFZ, (code0!=NULL)?code0->target:0, 0);
	nova3 = tac_create(TAC_JUMP, labelJMP, 0, 0);
	nova4 = tac_create(TAC_LABEL, labelIFZ, 0, 0);
 
	return tac_join(tac_join(tac_join(tac_join(tac_join(nova1, code0), nova2), code1), nova3), nova4);
}

/* code0: identificador da funcao, code1: corpo */
TAC* make_fundef(TAC* code0, TAC* code1, TAC* code2) {
	TAC* nova1;
	TAC* nova2;

	nova1 = tac_create(TAC_BEGINFUN, code0->target, 0, 0);
	nova2 = tac_create(TAC_ENDFUN, code0->target, 0, 0);
	return tac_join(tac_join(tac_join(nova1, code1), code2), nova2);
}

TAC* make_initVar(TAC* code0, TAC* code1, TAC* code2) {
	if(code1 == NULL) {
		return tac_create(TAC_VARI, (code2 != NULL) ? code2->target : NULL, (code0 != NULL) ? code0->target : NULL, 0);	
	}
	else{
		return tac_create(TAC_VARI, (code2 != NULL) ? code2->target : NULL, (code1 != NULL) ? code1->target : NULL, 0);	
	}
}


/* code0 = identifier, code1 = listInit, code2 = vecIndex */
TAC* make_initVec(TAC* code0, TAC* code1, TAC* code2) {
	int i;
	TAC* aux;
	TAC* result;

	if(code1 != NULL) {;
		aux = code1;
		for(i=0; i < atoi(code2->target->value); i++) {
			result = tac_join(tac_create(TAC_VECI, code0->target, aux->target, NULL), result);
			aux = aux->prev;
		}
		return result;
	}
	return NULL;
}
